package pl.edu.agh.student.janikhankus.gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.LayoutStyle.ComponentPlacement;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import pl.edu.agh.student.janikhankus.GrammarLexPar;
import pl.edu.agh.student.janikhankus.earley.MyTree;
import pl.edu.agh.student.janikhankus.earley.TreeMaker;
import pl.edu.agh.student.janikhankus.exceptions.BadProbabilitySumException;
import pl.edu.agh.student.janikhankus.exceptions.DuplicatedProductionsException;
import pl.edu.agh.student.janikhankus.exceptions.ParsingException;
import edu.uci.ics.jung.algorithms.layout.TreeLayout;
import edu.uci.ics.jung.graph.DelegateForest;
import edu.uci.ics.jung.graph.DirectedOrderedSparseMultigraph;
import edu.uci.ics.jung.graph.Forest;
import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.decorators.EdgeShape;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.renderers.Renderer;

/**
 * Class which represents graphical user interface
 * 
 * @author Jaroslaw Janik
 */
public class GrammarGUI {

	private JFrame frmEarleysParser;
	private JTable table;
	private JTextField textField;
	private JTextField textField_1;
	private JTextField textField_2;
	private JEditorPane editorPane;
	private JTabbedPane tabbedPane;
	private JFileChooser fileChooser = new JFileChooser();
	private JSlider slider;
	private Forest<MyNode, MyLink> graph = new DelegateForest<MyNode, MyLink>(new DirectedOrderedSparseMultigraph<MyNode, MyLink>());
	private VisualizationViewer<MyNode, MyLink> vv;
	private TreeLayout<MyNode, MyLink> treeLayout = new TreeLayout<MyNode, MyLink>(graph);
	private ArrayList<MyTree> myTrees;
	
	private List<Double> absoluteProbs = new LinkedList<Double>();
	double sumProbs = 0.0;
	
	private class MyLink {
		
		public String toString() {
			return "";
		}
	}
	
	private class MyNode {
		private String name;
		
		public MyNode(String name) {
			this.name = name;
		}
		
		public String toString() {
			return name;
		}
	}

	/**
	 * Starts application
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (Throwable e) {
			e.printStackTrace();
		}
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					GrammarGUI window = new GrammarGUI();
					window.frmEarleysParser.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Creates the application.
	 */
	public GrammarGUI() {
		initialize();
	}

	/**
	 * Initializes the contents of the frame.
	 */
	private void initialize() {
		frmEarleysParser = new JFrame();
		frmEarleysParser.setResizable(false);
		frmEarleysParser.setTitle("Probabilistic Earley's Parser");
		frmEarleysParser.setBounds(100, 100, 980, 610);
		frmEarleysParser.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		JMenuBar menuBar = new JMenuBar();
		frmEarleysParser.setJMenuBar(menuBar);
		
		JMenu mnFile = new JMenu("File");
		menuBar.add(mnFile);
		
		JMenuItem mntmSave = new JMenuItem("Save");
		mntmSave.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				int returnVal = fileChooser.showSaveDialog(frmEarleysParser);
				
				if (returnVal == JFileChooser.APPROVE_OPTION) {
					File file = fileChooser.getSelectedFile();
					
					try {
					    BufferedWriter out = new BufferedWriter(new FileWriter(file));
					    
					    String str = editorPane.getText();
					    out.write(str);
					    
					    out.close();	  
					} catch (IOException e1) {
						e1.printStackTrace();
					}
				}
			}
		});
		mnFile.add(mntmSave);
		
		JMenuItem mntmLoad = new JMenuItem("Load");
		mntmLoad.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				int returnVal = fileChooser.showOpenDialog(frmEarleysParser);
				
				if (returnVal == JFileChooser.APPROVE_OPTION) {
					File file = fileChooser.getSelectedFile();
					
					try {
						String fileText = "";
					    BufferedReader in = new BufferedReader(new FileReader(file));
					    String str;
					    while ((str = in.readLine()) != null) {
					        fileText += str + '\n';
					    }
					    in.close();
					    
					    editorPane.setText(fileText);
					} catch (IOException e1) {
						e1.printStackTrace();
					}
				}
			}
		});
		mnFile.add(mntmLoad);
		
		JMenuItem mntmExit = new JMenuItem("Exit");
		mntmExit.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				System.exit(0);
			}
		});
		mnFile.add(mntmExit);
		
		JPanel panel = new JPanel();
		GroupLayout groupLayout = new GroupLayout(frmEarleysParser.getContentPane());
		groupLayout.setHorizontalGroup(
			groupLayout.createParallelGroup(Alignment.LEADING)
				.addGroup(groupLayout.createSequentialGroup()
					.addContainerGap()
					.addComponent(panel, GroupLayout.DEFAULT_SIZE, 802, Short.MAX_VALUE)
					.addContainerGap())
		);
		groupLayout.setVerticalGroup(
			groupLayout.createParallelGroup(Alignment.LEADING)
				.addGroup(groupLayout.createSequentialGroup()
					.addContainerGap()
					.addComponent(panel, GroupLayout.DEFAULT_SIZE, 460, Short.MAX_VALUE)
					.addContainerGap())
		);
		
		tabbedPane = new JTabbedPane(JTabbedPane.TOP);
		
		JScrollPane scrollPane = new JScrollPane();
		
		textField = new JTextField();
		textField.setColumns(10);
		
		JButton btnParse = new JButton("Parse");
		btnParse.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				parseGrammar();
			}
		});
		
		JLabel lblGrammar = new JLabel("Grammar");
		
		JLabel lblWord = new JLabel("Word");
		
		slider = new JSlider();
		slider.setPaintTicks(true);
		slider.setMajorTickSpacing(1);
		slider.setMinorTickSpacing(1);
		slider.setMinimum(1);
		slider.setMaximum(10);
		slider.setPaintLabels(true);
		
		slider.addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent e) {
				JSlider source = (JSlider)e.getSource();
				
				int i = source.getValue() - 1;
				textField_1.setText(String.valueOf(absoluteProbs.get(i)));
				textField_2.setText(String.valueOf(normalize(absoluteProbs.get(i) / sumProbs)));
				drawGraph(i);
				drawChart(i);
		    }
		});
		
		JLabel lblProbability = new JLabel("Absolute prob.");
		
		textField_1 = new JTextField();
		textField_1.setEditable(false);
		textField_1.setColumns(10);
		
		textField_2 = new JTextField();
		textField_2.setEditable(false);
		textField_2.setColumns(10);
		
		JLabel lblRelativeProb = new JLabel("Relative prob.");
		GroupLayout gl_panel = new GroupLayout(panel);
		gl_panel.setHorizontalGroup(
			gl_panel.createParallelGroup(Alignment.TRAILING)
				.addGroup(gl_panel.createSequentialGroup()
					.addContainerGap()
					.addGroup(gl_panel.createParallelGroup(Alignment.LEADING, false)
						.addComponent(scrollPane, 0, 0, Short.MAX_VALUE)
						.addComponent(lblGrammar)
						.addComponent(textField)
						.addComponent(btnParse, GroupLayout.DEFAULT_SIZE, 252, Short.MAX_VALUE)
						.addComponent(lblWord))
					.addGap(18)
					.addGroup(gl_panel.createParallelGroup(Alignment.LEADING)
						.addGroup(gl_panel.createSequentialGroup()
							.addComponent(slider, GroupLayout.PREFERRED_SIZE, 483, GroupLayout.PREFERRED_SIZE)
							.addPreferredGap(ComponentPlacement.UNRELATED)
							.addGroup(gl_panel.createParallelGroup(Alignment.LEADING)
								.addComponent(textField_1, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
								.addComponent(lblProbability))
							.addPreferredGap(ComponentPlacement.RELATED)
							.addGroup(gl_panel.createParallelGroup(Alignment.LEADING)
								.addComponent(lblRelativeProb)
								.addComponent(textField_2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)))
						.addComponent(tabbedPane, GroupLayout.DEFAULT_SIZE, 584, Short.MAX_VALUE))
					.addContainerGap())
		);
		gl_panel.setVerticalGroup(
			gl_panel.createParallelGroup(Alignment.LEADING)
				.addGroup(gl_panel.createSequentialGroup()
					.addGap(13)
					.addGroup(gl_panel.createParallelGroup(Alignment.LEADING)
						.addGroup(gl_panel.createSequentialGroup()
							.addComponent(lblGrammar)
							.addPreferredGap(ComponentPlacement.RELATED)
							.addComponent(scrollPane, GroupLayout.PREFERRED_SIZE, 385, GroupLayout.PREFERRED_SIZE)
							.addPreferredGap(ComponentPlacement.UNRELATED)
							.addComponent(lblWord)
							.addPreferredGap(ComponentPlacement.RELATED)
							.addComponent(textField, GroupLayout.PREFERRED_SIZE, 29, GroupLayout.PREFERRED_SIZE))
						.addComponent(tabbedPane, GroupLayout.DEFAULT_SIZE, 465, Short.MAX_VALUE))
					.addPreferredGap(ComponentPlacement.RELATED)
					.addGroup(gl_panel.createParallelGroup(Alignment.LEADING)
						.addComponent(btnParse, GroupLayout.PREFERRED_SIZE, 40, GroupLayout.PREFERRED_SIZE)
						.addGroup(gl_panel.createSequentialGroup()
							.addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
								.addComponent(lblProbability)
								.addComponent(lblRelativeProb))
							.addPreferredGap(ComponentPlacement.RELATED)
							.addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
								.addComponent(textField_1, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
								.addComponent(textField_2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)))
						.addComponent(slider, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))
					.addContainerGap())
		);
		
        vv =  new VisualizationViewer<MyNode, MyLink>(treeLayout, new Dimension(100,100));
        vv.getRenderContext().setEdgeLabelTransformer(new ToStringLabeller<MyLink>());
        vv.getRenderContext().setEdgeShapeTransformer(new EdgeShape.Line<MyNode, MyLink>());
        vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller<MyNode>());
        vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.S);
        vv.setBackground(Color.white);
        
        final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse(); 	
        vv.setGraphMouse(graphMouse);
        
		tabbedPane.addTab("Tree", null, new GraphZoomScrollPane(vv), null);
		GroupLayout gl_tree = new GroupLayout(vv);
		gl_tree.setHorizontalGroup(
			gl_tree.createParallelGroup(Alignment.LEADING)
				.addGap(0, 481, Short.MAX_VALUE)
		);
		gl_tree.setVerticalGroup(
			gl_tree.createParallelGroup(Alignment.LEADING)
				.addGap(0, 421, Short.MAX_VALUE)
		);

		table = new JTable();
		table.setFillsViewportHeight(true);
		
		tabbedPane.addTab("Chart", null, new JScrollPane(table), null);
		
		editorPane = new JEditorPane();
		scrollPane.setViewportView(editorPane);
		panel.setLayout(gl_panel);
		frmEarleysParser.getContentPane().setLayout(groupLayout);
	}
	
	private void parseGrammar() {
		GrammarLexPar grammarParser = new GrammarLexPar(editorPane.getText());
		
		try {
			grammarParser.work();
			
			String[] words = textField.getText().split(" ");
			LinkedList<String> wordsList = new LinkedList<String>();
			for (String word : words) {
				wordsList.add(word);
			}
			
			myTrees = TreeMaker.makeTrees(wordsList, grammarParser.getGrammar());
			if (!myTrees.isEmpty()) { 
				System.out.println("succes"); 
				
				graph = new DelegateForest<MyNode, MyLink>(new DirectedOrderedSparseMultigraph<MyNode, MyLink>());	
				
				sumProbs = 0.0;
				absoluteProbs.clear();
				for (MyTree myTree : myTrees) {
					double prob = TreeMaker.computeProb(myTree);
					prob = normalize(prob);
					absoluteProbs.add(prob);
					sumProbs += prob;
				}
				
				slider.setMaximum(myTrees.size());
				if (slider.getValue() == 1) {
					drawGraph(0);
					drawChart(0);
					textField_1.setText(String.valueOf(absoluteProbs.get(0)));
					textField_2.setText(String.valueOf(normalize(absoluteProbs.get(0) / sumProbs)));
				} else {
					slider.setValue(1);
				}
			}
			else { 
				JOptionPane.showMessageDialog(frmEarleysParser, "Parse Error", "Cannot parse words", JOptionPane.ERROR_MESSAGE);
			}
			
		} catch (ParsingException pe) {
			JOptionPane.showMessageDialog(frmEarleysParser, pe.getMessage(), "Grammar Error", JOptionPane.ERROR_MESSAGE);
		} catch (BadProbabilitySumException e1) {
			JOptionPane.showMessageDialog(frmEarleysParser, "Bad Production Probability", "Grammar Error", JOptionPane.ERROR_MESSAGE);
		} catch (DuplicatedProductionsException e1) {
			JOptionPane.showMessageDialog(frmEarleysParser, "Double Productions", "Grammar Error", JOptionPane.ERROR_MESSAGE);
		} catch (Exception e1) {
			JOptionPane.showMessageDialog(frmEarleysParser, "Grammar Syntax Error", "Grammar Error", JOptionPane.ERROR_MESSAGE);
		}
	}
	
	private void prepareTree(Forest<MyNode, MyLink> graph, MyNode node, MyTree myTree) {
		LinkedList<MyTree> stack = new LinkedList<MyTree>();
		LinkedList<MyNode> stack2 = new LinkedList<MyNode>();

		MyNode newNode = new MyNode(myTree.toString());
		
		stack.add(myTree);
		stack2.add(newNode);
		
		graph.addVertex(newNode);
		
		while (!stack.isEmpty())
		{
			MyTree mainTree = stack.pollFirst();
			newNode = stack2.pollFirst();
			
			ArrayList<MyTree> children = mainTree.getChilder();
			
			if (children != null)
			{
				for (MyTree tree : children) {
					MyNode newNode2 = new MyNode(tree.toString());
					graph.addVertex(newNode);
					graph.addEdge(new MyLink(), newNode, newNode2);
					stack.add(tree);
					stack2.add(newNode2);
				}
			}
		}
	}
	
	private void tabRefresh() {
		tabbedPane.repaint();
	}
	
	private void drawGraph(int i) {
		graph = new DelegateForest<MyNode, MyLink>(new DirectedOrderedSparseMultigraph<MyNode, MyLink>());
		MyNode node = new MyNode(myTrees.get(i).toString());
		prepareTree(graph, node, myTrees.get(i));
		
		treeLayout.setGraph(graph);
		
		tabRefresh();   
	}
	
	private void drawChart(int i) {
		table = TreeMaker.getJTable(myTrees.get(i));
		
		tabbedPane.setComponentAt(1, new JScrollPane(table));
		
		tabRefresh();
	}
	
	private double normalize(double value) {
		value = Math.round(value * 1000000000);
		value /= 1000000000;
		
		return value;
	}
}
